home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Libris Britannia 4
/
science library(b).zip
/
science library(b)
/
CUGUK
/
COMMS
/
C100.ZIP
/
KERMIT.ZIP
/
CKXKER.BLD
< prev
next >
Wrap
Text File
|
1990-01-31
|
51KB
|
1,513 lines
Date: 23 May 1988
From: Adrian Godwin, Thorn EMI Micrologic, Bedford, England.
Subject: C-Kermit 4E(070) Implementation Notes for Minix
These notes discuss changes I made to the 4E(070) C-Kermit to run
it efficiently under Minix 1.1. The earlier 4D(061) version was used
originally - the changes to kermit have been slightly simplified in this
version. Kermit source code changes (principally in ckufio and ckutio)
are attached. The changes are sufficiently minor that they may be supported
through conditional compilation of the standard Unix file set, but the
modified files have all been given names of the form ckx..., pending full
testing of the modifications under other systems.
Hints, fixes and library changes are also attached - most of
these are applicable for anyone implementing a serial i/o driver for Minix,
and many library fixes are useful for porting other utilities.
Kermit cannot be built under version 1.1 Minix, as it compiles to
about 85K and the initial Minix assembler cannot produce separate I&D model
output. The executable file ckxker.out was therefore built under MS-DOS
using the Lattice 3.10 C compiler. Some care is needed in cross-compiling :
see the notes about library building in Tanenbaum's book, and read the
Lattice makefile, ckxker.mak. The library functions setjmp() and longjmp(),
in particular, will need recoding to suit the compiler you use.
Associated Files:
=================
I have included with this build file (ckxker.bld) the following :
ckxtio.c - Modified Kermit source files
ckxfio.c
ckxker.mak - Makefile for Lattice 'lmk' and 3.10 compiler.
ckxker.inp - linker command file (included in ckxker.mak)
ckxker.out - Minix executable for use if you can't build
Kermit yourself under MS-DOS.
ckxker.boo - ascii - encoded executable file for ease of
transfer.
The following filter (based on the ms-kermit example) may be used to
'deboo' ckxker.boo into a Minix executable. The filename embedded in the
.boo file is ignored - direct standard output to 'kermit'.
#include <stdio.h>
#define fixchar(x) ((x)-'0')
#define NULLCHR fixchar('~')
main()
{
int a,b,c,d,repeat;
/* skip the line with the filename */
while(getchar() != '\n')
;
while( (c = getchar()) != EOF) {
if (c == '\n')
continue;
if (fixchar(c) == NULLCHR) {
repeat = fixchar(getchar());
while (repeat-- > 0)
putchar(0);
}
else {
a = fixchar(c);
b = fixchar(getchar());
c = fixchar(getchar());
d = fixchar(getchar());
putchar( ((a*4) + (b/16)) & 0xff);
putchar( ((b*16) + (c/4)) & 0xff);
putchar( ((c*64) + d) & 0xff);
}
}
_cleanup();
}
Testing
=======
The executable file has been tested against itself (a second kermit
process on the same machine), against Kermit-32 3.3.111 on VAX/VMS, and
against MS-Kermit 2.30 on a PC-AT. All successfully transferred files and
performed host commands, such as 'help' and 'dir'. The version has not
been tested exhaustively - in particular the parity and flowcontrol options
for IBM, and the dialling and script commands, have not been tested at all.
Performance was not particularly good - 22% to 30% on receive
(depending on whether a Minix - or Lattice - compiled kernel was used) and
up to 39% on send - but this is a general problem with C-Kermit. The PS/2
mentioned above managed only 31% efficiency on receiving from the same
lightly - loaded VAX.
All testing was performed at 9600 baud. Testing at 4800 baud gave
a significant reduction in errors, but not enough to improve on the 9600
baud throughput. A worthwhile improvement was obtained by reducing timeouts
to 1 to 2 seconds.
The integration of Minix conditional compilation options has been
tested by successfully making 'SCO286' on a PS/2 model 60 and 'SYS3'
on a Convergent Technologies Mini-20. I have not been able to test a
'make V7', where bugs are most likely to have been introduced.
Changes to Kermit:
==================
ckutio.c Most of the changes are in this module. Although Minix is
closely based on V7 UNIX, the V7 conditionals in this module
are almost exclusively to enable the use of kernel buffer
peeking for typahead determination. Minix does not have a V7
kernel, so this won't work. Minix does, however, permit non-
blocking reads. The V7 define used elsewhere for MINIX
compatibility is #undefined here - a new symbol, V7_MINIX
is defined for selecting the code that is common to both.
Kermit will work perfectly well using MYREAD, but for
trapping console aborts during file transfer, the current
implementation needs to know if there are any characters in
the kernel buffers. It is worth implementing a FIONREAD
ioctl call to permit this.
To save disc space, I've dummied out the uucp tty locking
code for the Minix version. It can easily be restored if
required.
For some reason, Lattice returns the unsigned char result
of myread() expanded to a signed int. A badly read character
can thus appear to other functions as an error code. Make
myread return ((int)ch) & 0377.
ckufio.c No include file <sys/files.h>. Lattice would prefer the
functions FILE *fopen(), *fdopen() to be declared as such.
The function zclosif() attempts to kill a completed child
process. If it has already completed, this fails, but MINIX
still wants a wait() call to clean up the memory allocation,
otherwise a few invocations of 'remote directory' will use
up any remaining memory. This problem will also occur in the
minix shell if you invoke all processes in the background.
Use the shell command 'wait' to clean up completed processes
(zombies).
Changes to Minix library routines.
==================================
I started developing this set of library fixes when building Micro-
Emacs. A few of them may be unnecessary, but I recommend you fix them anyway.
Many routines are here because they aren't in the standard library.
The lattice makefile includes all these routines as an object called
'emlib' - this is for debugging purposes only (they should be built into
the library). While not strictly a library change, fix dos2out before
attempting to convert this large executable : the calculation of load_size
will be wrong unless all the operands are forced to type long. The
calculation of the total memory allocation, a_totb is also wrong, since it
includes (for the separate I & D model) the text size a_text in the sum.
This results in a 154K memory allocation, which is not permitted.
chmem =20000
will fix it. The .exe header generated by the Microsoft linker places 0 in
the 'bytes in last page' slot for a load module which exactly fits in an
integral number of sectors. This is misunderstood by dos2out in two places :
it makes an entry in the header for a data segment size 512 bytes too small,
and copies 512 bytes too few to the output file. This may cause hard-to-find
bugs that appear and disappear on relinks.
Some include file changes. Stdio.h defines a macro 'puts()' as an
fputs() to stdout. This is wrong, as puts() should append a newline while
fputs() doesn't. Remove the macro and create a library function.
There is no <sys/dir.h>. It should contain:
/* dir.h
*
* The structure of a directory entry.
*/
#define NAME_SIZE 14 /* defined in fs/const.h */
struct direct {
inode_nr d_ino; /* inode number */
char d_name[NAME_SIZE]; /* name fills it out to 16 bytes */
} ;
The file h/type.h is needed by kermit, but included as <sys/types.h>.
Create a <sys/types.h> that includes h/type.h.
Sgtty.h also needs some changes to define tty functions that don't
exist in standard Minix. The kermit I have built used this version of sgtty.h,
so if you want to use that executable, your tty driver must correspond. The
i/o functions are an upwards compatible superset of the Minix sgtty functions,
and are inspired by SCO Xenix. Note particularly the implementation of
stty() and gtty() as macros.
/* sgtty.h
*
* Data structures for IOCTL.
*/
struct sgttyb {
char sg_ispeed; /* input speed (has precedence) */
char sg_ospeed; /* output speed (may be ignored) */
char sg_erase; /* erase character */
char sg_kill; /* kill character */
int sg_flags; /* mode flags */
};
/* simulate stty and gtty with ioctl */
#define stty(fd, arg) ioctl(fd, TIOCSETP, arg)
#define gtty(fd, arg) ioctl(fd, TIOCGETP, arg)
struct tchars {
char t_intrc; /* SIGINT char */
char t_quitc; /* SIGQUIT char */
char t_startc; /* start output (initially CTRL-Q) */
char t_stopc; /* stop output (initially CTRL-S) */
char t_eofc; /* EOF (initially CTRL-D) */
char t_brkc; /* input delimiter (like nl) */
};
/* Fields in sg_flags. */
#define COOKED 0000000 /* neither CBREAK nor RAW */
#define TANDEM 0000001 /* XON XOFF flowcontrol on input */
#define CBREAK 0000002 /* enable cbreak mode */
#define LCASE 0000004 /* support lower case */
#define ECHO 0000010 /* echo input */
#define CRMOD 0000020 /* map lf to cr + lf */
#define RAW 0000040 /* enable raw mode */
#define ODDP 0000100 /* odd parity */
#define EVENP 0000200 /* even parity */
#define ANYP 0000300 /* parity not set / ignored */
#define XTABS 0006000 /* do tab expansion */
#define TIOCGETP (('t'<<8) | 8)
#define TIOCSETP (('t'<<8) | 9)
#define TIOCGETC (('t'<<8) | 18)
#define TIOCSETC (('t'<<8) | 17)
/*
* I can't find a 'standard' definition for this one, so I've assigned
* a '1' arbitrarily.
*/
#define FIONREAD (('f'<<8) | 1)
/* Baud rate settings for async ttys. */
#define B0 0 /* hangup line (drop DTR) */
#define B50 1
#define B75 2
#define B110 3
#define B134 4
#define B150 5
#define B200 6
#define B300 7
#define B600 8
#define B1200 9
#define B1800 10
#define B2400 11
#define B4800 12
#define B9600 13
#define EXTA 14
#define B19200 EXTA
#define EXTB 15
#define B38400 EXTB
There are no time management calls in MINIX other than time, times()
etc. I have defined asctime() and gmtime() functions based on the conversions
done in date.c, but avoided support for timezones and daylight saving.
/* time.h : structure and function definitions for time library calls. */
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst; /* not currently supported */
};
extern struct tm *localtime();
extern struct tm *gmtime();
extern char *asctime();
/* system(command)
*
* Code copied from the MINIX make utility 'mysystem' function, with
* minor modifications.
*/
char *_defpath = "/bin/sh";
system(cmd)
char *cmd;
{
int ccode,pid,status;
char *shell, *getenv();
if ( (shell = getenv("SHELL")) == NULL)
shell = _defpath;
if ( (pid = fork()) == 0 ) { /* child execs a shell */
execl(shell,shell,(*cmd ? "-c" : "-i"),cmd,0);
}
if ( pid < 0 ) { /* parent waits for child */
return(pid);
}
else {
while ( ((ccode = wait(&status)) != pid) && (ccode != -1))
;
return(status);
}
}
/* This function copied from the MINIX library, but with some bugs fixed. */
char *getenv(name)
register char *name;
{
extern char **environ;
register char **v = environ, *p, *q;
while ((p = *v++) != NULL) { /* fix : increment v */
q = name;
while (*p++ == *q)
if (*q++ == 0)
break; /* fix : break rather than continue */
if (*(p - 1) != '=')
continue;
return(p);
}
return(0);
}
/* getc
*
* fixed to ensure _count always indicates number of chars in the buffer.
* - used to indicate correctly only when zero. Decrement _count after
* taking char from recently filled buffer. Note : fseek() had a bodge
* which allowed it to work with this error. This also requires a fix.
*
* note when rebuilding library : this module should appear AFTER scanf,
* which calls it, to ensure it can be found.
*/
#include "stdio.h"
getc(iop)
FILE *iop;
{
int ch;
if ( testflag(iop, (_EOF | _ERR )))
return (EOF);
if ( !testflag(iop, READMODE) )
return (EOF);
if (--iop->_count < 0){ /* changed from <= 0 */
if ( testflag(iop, UNBUFF) )
iop->_count = read(iop->_fd,&ch,1);
else
iop->_count = read(iop->_fd,iop->_buf,BUFSIZ);
if (iop->_count <= 0){
if (iop->_count == 0)
iop->_flags |= _EOF;
else
iop->_flags |= _ERR;
return (EOF);
}
else {
iop->_ptr = iop->_buf;
iop->_count--; /* inserted */
}
}
if (testflag(iop,UNBUFF))
return (ch & CMASK);
else
return (*iop->_ptr++ & CMASK);
}
/* ttyname() - missing library function. Gets the inode number of the
* passed filedescriptor, then searches the /dev directory for a match.
* Returns a pointer to the name (static data) or NULL.
*/
#include "stdio.h"
#include "stat.h"
#include "dir.h"
char *ttyname(fd)
int fd;
{
struct stat s;
int n, dd;
char *p = NULL;
static struct direct d[2]; /* big enough to nul-terminate name */
if (fstat(fd, &s) != 0 || (s.st_mode&S_IFMT) != S_IFCHR)
return(NULL);
/* isatty. now search /dev for the inode. */
n = s.st_ino;
if ((dd = open("/dev", 0)) < 0)
return NULL; /* cannot read directory */
while (read( dd, d, sizeof(*d)) == sizeof(*d))
if (d[0].d_ino == n) {
p = d[0].d_name;
p[sizeof(*d)] = '\0';
break;
}
close(dd);
return(p);
}
/* fdopen() - missing library function. Returns a file pointer set up from
* the passed file descriptor, or NULL if it can't.
*/
#include "stdio.h"
FILE *fdopen(fd,mode)
int fd;
char *mode;
{
register int i;
FILE *fp;
char *malloc();
int flags = 0;
for (i = 0; _io_table[i] != 0 ; i++)
if ( i >= NFILES )
return(NULL);
switch(*mode){
case 'w':
case 'a':
flags |= WRITEMODE;
break;
case 'r':
flags |= READMODE;
break;
default:
return(NULL);
}
if (( fp = (FILE *) malloc (sizeof( FILE))) == NULL )
return(NULL);
fp->_count = 0;
fp->_fd = fd;
fp->_flags = flags;
fp->_buf = malloc( BUFSIZ );
if ( fp->_buf == NULL )
fp->_flags |= UNBUFF;
else
fp->_flags |= IOMYBUF;
fp->_ptr = fp->_buf;
_io_table[i] = fp;
return(fp);
}
/* modified exit() which flushes stdio buffers before dying. This means
* standard io library is included whether you wanted it or not - add a
* dummy "_cleanup() {}" declaration to avoid it if you're writing very
* small programs like the Minix utilities. This is more likely to
* be compatible with other UNIX programs than the exit() in the standard
* release.
*/
#include "../include/lib.h"
int exit(status)
int status;
{
_cleanup();
return callm1(MM, EXIT, status, 0, 0, NIL_PTR, NIL_PTR, NIL_PTR);
}
/* strncmp() - library module, changed to operate correctly when
* n is zero. (causes problem in kermit interactive command processor)
*/
int strncmp(s1, s2, n)
register char *s1, *s2;
int n;
{
/* Compare two strings, but at most n characters. */
while (n-- != 0) {
if (*s1 != *s2) return(*s1 - *s2);
if (*s1 == 0) return(0);
s1++;
s2++;
}
return 0;
}
/* These functions based on the similar operations in date.c. No support
* for timezones or daylight saving - localtime() currently just calls
* gmtime() and returns the results unmodified.
*/
#include <time.h>
static int _days_per_month[] =
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
static char *_months[] =
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
static char *_days[] =
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static long _s_p_min = 60L;
static long _s_p_hour = 60L * 60L;
static long _s_p_day = 60L * 60L * 24L;
static long _s_p_year = 60L * 60L * 24L * 365L;
char *asctime(tmp)
struct tm *tmp;
{
static char _asctbuf[30];
sprintf(_asctbuf,"%s %s %2d %02d:%02d:%02d %d\n",
_days[tmp->tm_wday], _months[tmp->tm_mon],
tmp->tm_mday, tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
tmp->tm_year + 1900);
return _asctbuf;
}
struct tm *gmtime(ltp)
long *ltp;
{
static struct tm _tm;
char *p;
long t = *ltp;
for (p = (char *)&_tm; p < ((char *)&_tm) + sizeof(_tm); p++)
*p = 0;
/* get day-of-week. (Add 4 because 1.1.70 was a Thursday) */
_tm.tm_wday = ((t / _s_p_day)+4L) % 7L;
/* reduce t by years and days, leaving seconds this year. */
while (t >= _s_p_year) {
if (((_tm.tm_year + 2) % 4) == 0)
t -= _s_p_day;
_tm.tm_year += 1;
t -= _s_p_year;
}
_tm.tm_yday = t / _s_p_day;
/* adjust february for leap year and reduce to seconds this month */
if (((_tm.tm_year + 2) % 4) == 0)
_days_per_month[1]++;
while ( t >= (_days_per_month[_tm.tm_mon] * _s_p_day))
t -= _days_per_month[_tm.tm_mon++] * _s_p_day;
_days_per_month[1] = 28;
_tm.tm_year += 70;
/* find day of month */
_tm.tm_mday = 1;
while (t >= _s_p_day) {
t -= _s_p_day;
_tm.tm_mday++;
}
/* and time today */
while (t >= _s_p_hour) {
t -= _s_p_hour;
_tm.tm_hour++;
}
while (t >= _s_p_min) {
t -= _s_p_min;
_tm.tm_min++;
}
_tm.tm_sec = (int) t;
return &_tm;
}
struct tm *localtime(ltp)
long *ltp;
{
return gmtime(ltp);
}
/* Modify do_printf to accept the K&R specification of %ld, %lx for
* long integer printf formats. The change is trivial, and the Minix
* %D, %X format can still be supported.
*/
( not all the function listed here ............. )
~~~~~~~~
line 16
~~~~~~~~
int r,
w1, w2,
sign,
lformat;
long l;
~~~~~~~~~~~~~~~
line 36
~~~~~~~~~~~~~~~
padchar = ' ';
lformat = 0;
format++;
~~~~~~~~~~~~~~~
after line 60
~~~~~~~~~~~~~~~
if ( *format == 'l'){
lformat = 0x80;
format++;
}
switch (*format + lformat) {
case 'd':
l = (long) GET_ARG(vl, int);
~~~~~~~~~~~~~~~~~
line 76
~~~~~~~~~~~~~~~~~
break;
case 'x':
l = (long) GET_ARG(vl, int);
if (l < 0) l = l & 0xFFFF;
r = 16;
break;
case 'D':
case 'd' + 0x80:
l = (long) GET_ARG(vl, long);
r = 10;
break;
case 'O':
case 'o' + 0x80:
l = (long) GET_ARG(vl, long);
r = 8;
break;
case 'X':
case 'x' + 0x80:
l = (long) GET_ARG(vl, long);
r = 16;
break;
case 'c':
c = (char) GET_ARG(vl, int);
/* char's are casted back to int's */
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~
The following fixes may be regarded as dubious, since they actually make
the operation of minix less like standard unix. However, the performance
of unbuffered tty output (as used in kermit help messages and command
completion) is so appalling I felt something was needed.
If a call is made to any of the string output routines (printf, puts ..),
when the channel is unbuffered, then a temporary buffer is attached to
the channel for the duration of the call and flushed at the end. Since
setting stdout unbuffered freed it's buffer allocation, the buffer used
for this is probably already available on the heap.
The speed-up effect of this on unbuffered i/o (as used for all stderr
in the system utilities) is startling, and I can't think why these changes
should cause any incompatibilities with standard Unix usage.
#include <stdio.h>
/* tmpbuf() creates a temporary buffer for the file, if possible.
* Only call this if the file is unbuffered.
*/
static char *_locbuf = NULL;
static int _locflags;
static void tmpbuf(file)
register FILE *file;
{
if (_locbuf == NULL) /* no buffer - get one */
_locbuf = malloc(BUFSIZ);
if (_locbuf == NULL) /* no space - cannot buffer */
return;
_locflags = file->_flags;
file->_flags &= ~UNBUFF;
file->_buf = file->_ptr = _locbuf;
file->_count = 0;
}
/* endbuf() restores the FILE flags and flushes the buffer if required. */
static endbuf(file, fflag)
register FILE *file;
int fflag;
{
if (fflag || file->_buf == _locbuf)
fflush(file);
if (file->_buf == _locbuf && _locbuf != NULL) {
/* temporary buffer used - restore old settings */
file->_buf = file->_ptr = NULL;
file->_count = 0;
file->_flags = _locflags;
}
}
/* puts() - replaces macro in stdio.h, since a newline is required
* after the given string.
*/
puts(s)
register char *s;
{
if ( testflag(stdout, UNBUFF) )
tmpbuf(stdout);
while ( *s )
putc(*s++,stdout);
putc('\n',stdout);
endbuf(stdout, 1); /* always flush */
}
fputs(s,file)
register char *s;
FILE *file;
{
if ( testflag(file, UNBUFF) )
tmpbuf(file);
while ( *s )
putc(*s++,file);
endbuf(file, 0);
}
fprintf (file, fmt, args)
FILE *file;
char *fmt;
int args;
{
if ( testflag(file, UNBUFF) )
tmpbuf(file);
_doprintf (file, fmt, &args);
endbuf(file, testflag(file, PERPRINTF));
}
printf (fmt, args)
char *fmt;
int args;
{
if ( testflag(stdout, UNBUFF) )
tmpbuf(stdout);
_doprintf (stdout, fmt, &args);
endbuf(stdout, testflag(stdout, PERPRINTF));
}
Changes to Minix
================
Here's where it gets more experimental: I've done the following fixes
to MINIX in order to get Kermit working or to solve problems I've found along
the way. Some may not be essential - I didn't want to rebuild with all my
older fixes taken out just to determine which are necessary. Since all the
fixes are for major problems, they're worth doing anyway.
Any SIGALRM sent to a process will cancel all pending alarms. This
has the effect of suspending forever (say) the update process if another
process executes sleep(). MM/signal.c, check_sig() resets not only the
alarm bit for process getting the signal, but for all others. Resetting the
bit needs to be conditional on the state of the send_sig flag. The clock
task looks in the message field CLOCK_PROC_NR for the process number of
the task that needs the alarm signal. MM actually puts the number in the
field PROC_NR, which is not the same. Hence the clock actually sends the
signal to process 0, which is taken to mean all processes.
It is possible for a message from FS to a task to be overwritten
before it reaches its destination if the task supports multiple minor
devices and process suspension, as a TTY driver with more than one line
would. This will occur if the TTY driver has suspended a task, and is busy
with an interrupt request (which will revive the task) when FS send-receives
a message about a different minor device. FS will be forced to wait for the
task until the REVIVE message appears. FS is then sent the message, since it
appears to be waiting for a reply. The tty task will then look for more
messages and will find the FS request, now overwritten with the REVIVE message.
The TTY will be given its own REVIVE message and reject it as invalid. FS
will continue to wait for a reply to it's TTY request (now lost) and hang.
I have fixed this by treating REVIVE messages in the same way as
signals they are rather similar, since they are sent to a caller which is not
waiting for them. Instead of replying with a REVIVE message, I call
set_revive(), a function similar to cause_sig() which sets a flag in the
proc table p_flags and saves the reply status in a new member, p_status.
Inform() is then called for FS as well as MM, and searches for the revive
flag. Any pending revives are actioned as a HARDWARE message, just like a
kernel signal. FS will then no longer get a reply from a previous message
when send-receiving to a task, since the REVIVE call comes from HARDWARE.
FS can thus be simplified in areas like rw_dev().
FS may panic with a "couldn't revive anyone" error when it recovers
a process from waiting on a broken pipe. If the pipe is reading from two file
descriptors (as in the Kermit procedure to pipe process stdout and stderr to
a remote machine for the REMOTE HOST command), then FS/misc.c, do_exit() will
call FS/open.c, do_close() for both descriptors. On each call, FS/pipe.c,
release() will be called, setting the REVIVING flag for the reading process (e.g. the
Kermit) twice, and also incrementing 'reviving'. FS/main.c, get_work() revives
the reading process, then can't find a process to account for the other
increment of 'reviving'. release() should check for the REVIVING flag before
calling revive() again.
FS can send a cancel message for an out-of-range tty number when a
process is killed, due to FS/pipe.c, do_unpause() not setting up the global
fp before calling get_filp(). The tty driver does not catch out-of-range line
numbers on either messages or characters from the overrun buffer, and can
index a tty structure that doesn't exist.
Do_unpause() doesn't check that get_filp() returns a valid result.
The return can be NULL if the file (that the process is paused on) has been
closed. This happens if a signal killed the process. The distributed code of
MM/signal.c, check_sig() differs from the printed listings in Tanenbaum's
book in the order of execution of unpause() and sig_proc(). This means that
FS expects do_unpause to have a process to restart, when the filehandles it
should look at are already closed. It seems OK to return the order of the
calls to their original state, but I'd like to know why they changed.
CONTROL-S will pause the user process by waiting before replying to
FS. This causes the entire system to get stuck waiting for FS to return from
the TTY call. The TTY needs to SUSPEND the process on output, just as it does
on input. Do_cancel() in the TTY driver also has to change to ensure that FS
obtains a reply or a REVIVE when output is completed.
The following comments are not bug reports, but code changes I have done
to improve the system's performance at high interrupt rates. I found
character processing at 9600 baud on an 8MHz 8086 a problem, and had to
make several changes :
When interrupts are arriving at high speed (characters at 9600 baud),
some may be lost due to the time taken to process an interrupt in the kernel.
This time may be reduced by moving the check for now-ready tasks in kernel/
proc.c, interrupt() to the end of mini-rec(). This reduces the number of calls
to mini-send within the interrupt handler to 1. If a clock interrupt was
pending, the clock processing can double or triple the time needed to process
the call to interrupt(). If the tty task is often scheduled, the clock task
is often behind, since tasks do not preempt each other. Thus clock interrupts
often are pending when interrupt() is called at high repetition rates.
However, placing this check in mini_rec() will mean that interrupts
will always be serviced when the task becomes ready. This, while reducing the
interrupt latency of a task, means that fast interrupts can make the task
permanently computable. No other process can then obtain any time to read
the incoming characters, and the task's buffer fills. Flowcontrol and possibly
ignoring unbufferable input makes it possible to empty the buffer again.
File transfer with Kermit is OK, because the packets are considerably smaller
than the task's buffer, but packets longer than the buffer, sliding windows
and connect sessions will have problems if flowcontrol is not possible.
The mini_send() call in interrupt() should also be removed and the
message copy be performed directly within interrupt(), avoiding the
substantial checking performed on call parameters, which should be unnecessary
on messages within the kernel. The message copy might also be performed by
a faster version of copy_mess(), for use within the kernel, which need not
span segments. Better still, since interrupts may overwrite each other and
only affect a task's private data area, they tend to use a single known
buffer for data transfer to the calling task. (I haven't made these last 2 changes yet).
The above changes permitted rather operation in connect mode at 9600
baud on an 8Mhz 8086, using a kernel compiled with the Lattice compiler.
The interrupt routine was taking most of the time - the application
program could only run when a break in transmission occurred. Since I use
the system mostly with a modem at 1200 baud, this was acceptable. An AT
based system could probably do better, but the Minix compiler produces
poorer code than Lattice, and a Minix-built kernel will be a little slower.
The following code fragments indicate most of the above fixes. Since the code
was originally that distributed by Prentice-Hall, I have included their
copyright notice.
/* Copyright (C) 1987 by Prentice-Hall, Inc. Permission is hereby granted to
* private individuals and educational institutions to modify and
* redistribute the binary and source programs of this system to other
* private individuals and educational institutions for educational and
* research purposes. For corporate or commercial use, permission from
* Prentice-Hall is required. In general, such permission will be granted,
* subject to a few conditions.
*/
=============
Kernel/proc.c
=============
/*===========================================================================*
* interrupt *
*===========================================================================*/
PUBLIC interrupt(task, m_ptr)
int task; /* number of task to be started */
message *m_ptr; /* interrupt message to send to the task */
{
/* An interrupt has occurred. Schedule the task that handles it. */
int this_bit;
register struct proc *dest_ptr;
#ifdef ibmpc
/* Re-enable the 8259A interrupt controller. */
this_bit = 1 << (-task);
port_out(INT_CTL, ENABLE); /* this re-enables the 8259A controller chip */
if (pc_at) port_out(INT2_CTL, ENABLE); /* re-enable second 8259A */
#endif
/* Try to send the interrupt message to the indicated task. */
this_bit = 1 << (-task);
dest_ptr = proc_addr(task);
if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING)
&& (dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == HARDWARE) ) {
/* Destination is indeed waiting for this message. */
cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys, m_ptr,
dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */
if (dest_ptr->p_flags == 0) ready(dest_ptr);
busy_map &= ~this_bit; /* turn off the bit in case it was on */
} else {
/* The message could not be sent to the task; it was not waiting. */
if (task == CLOCK) {
lost_ticks++;
} else {
busy_map |= this_bit; /* mark task as busy */
task_mess[-task] = m_ptr; /* record message pointer */
}
}
/* If a task has just been readied and a user is running, run the task. */
if (rdy_head[TASK_Q] != NIL_PROC && (cur_proc >= 0 || cur_proc == IDLE))
pick_proc();
}
/*===========================================================================*
* mini_send *
*===========================================================================*/
PUBLIC int mini_send(caller, dest, m_ptr)
int caller; /* who is trying to send a message? */
int dest; /* to whom is message being sent? */
message *m_ptr; /* pointer to message buffer */
{
/* Send a message from 'caller' to 'dest'. If 'dest' is blocked waiting for
* this message, copy the message to it and unblock 'dest'. If 'dest' is not
* waiting at all, or is waiting for another source, queue 'caller'.
*/
register struct proc *caller_ptr, *dest_ptr, *next_ptr;
vir_bytes vb; /* message buffer pointer as vir_bytes */
vir_clicks vlo, vhi; /* virtual clicks containing message to send */
vir_clicks len; /* length of data segment in clicks */
/* User processes are only allowed to send to FS and MM. Check for this. */
if (caller >= LOW_USER && (dest != FS_PROC_NR && dest != MM_PROC_NR))
#ifdef DEBUG
if (dest != SYSTASK) return(E_BAD_DEST);
#else
return(E_BAD_DEST);
#endif
caller_ptr = proc_addr(caller); /* pointer to source's proc entry */
dest_ptr = proc_addr(dest); /* pointer to destination's proc entry */
if (dest_ptr->p_flags & P_SLOT_FREE) return(E_BAD_DEST); /* dead dest */
/* Check for messages wrapping around top of memory or outside data seg. */
len = caller_ptr->p_map[D].mem_len;
vb = (vir_bytes) m_ptr;
vlo = vb >> CLICK_SHIFT; /* vir click for bottom of message */
vhi = (vb + MESS_SIZE - 1) >> CLICK_SHIFT; /* vir click for top of message */
if (vhi < vlo || vhi - caller_ptr->p_map[D].mem_vir >= len)return(E_BAD_ADDR);
/* Check to see if 'dest' is blocked waiting for this message. */
if ( ((dest_ptr->p_flags & (RECEIVING | SENDING)) == RECEIVING) &&
(dest_ptr->p_getfrom == ANY || dest_ptr->p_getfrom == caller) ) {
/* Destination is indeed waiting for this message. */
cp_mess(caller, caller_ptr->p_map[D].mem_phys, m_ptr,
dest_ptr->p_map[D].mem_phys, dest_ptr->p_messbuf);
dest_ptr->p_flags &= ~RECEIVING; /* deblock destination */
if (dest_ptr->p_flags == 0) ready(dest_ptr);
} else {
/* Destination is not waiting. Block and queue caller. */
if (caller == HARDWARE) return(E_OVERRUN);
caller_ptr->p_messbuf = m_ptr;
caller_ptr->p_flags |= SENDING;
unready(caller_ptr);
/* Process is now blocked. Put in on the destination's queue. */
if ( (next_ptr = dest_ptr->p_callerq) == NIL_PROC) {
dest_ptr->p_callerq = caller_ptr;
} else {
while (next_ptr->p_sendlink != NIL_PROC)
next_ptr = next_ptr->p_sendlink;
next_ptr->p_sendlink = caller_ptr;
}
caller_ptr->p_sendlink = NIL_PROC;
}
#ifdef DEBUG
/* This message has been successfully handled. See if it should be saved
* in the message log.
*/
if ((save_flags[caller+NR_TASKS] & CALL_LOG)
|| (save_flags[dest +NR_TASKS] & DEST_LOG))
save_msg(caller, dest, m_ptr);
#endif
return(OK);
}
/*===========================================================================*
* mini_rec *
*===========================================================================*/
PRIVATE int mini_rec(caller, src, m_ptr)
int caller; /* process trying to get message */
int src; /* which message source is wanted (or ANY) */
message *m_ptr; /* pointer to message buffer */
{
/* A process or task wants to get a message. If one is already queued,
* acquire it and deblock the sender. If no message from the desired source
* is available, block the caller. No need to check parameters for validity.
* Users calls are always sendrec(), and mini_send() has checked already.
* Calls from the tasks, MM, and FS are trusted.
*/
register struct proc *caller_ptr, *sender_ptr, *prev_ptr;
int sender, this_bit;
int locked = FALSE;
caller_ptr = proc_addr(caller); /* pointer to caller's proc structure */
/* if we're ready to receive, find a sender. Else just block. */
if ((caller_ptr->p_flags & SENDING) == 0) {
/* Check to see if a message from desired source is already available. */
sender_ptr = caller_ptr->p_callerq;
while (sender_ptr != NIL_PROC) {
sender = sender_ptr - proc - NR_TASKS;
if (src == ANY || src == sender) {
/* An acceptable message has been found. */
cp_mess(sender, sender_ptr->p_map[D].mem_phys, sender_ptr->p_messbuf,
caller_ptr->p_map[D].mem_phys, m_ptr);
sender_ptr->p_flags &= ~SENDING; /* deblock sender */
if (sender_ptr->p_flags == 0) ready(sender_ptr);
if (sender_ptr == caller_ptr->p_callerq)
caller_ptr->p_callerq = sender_ptr->p_sendlink;
else
prev_ptr->p_sendlink = sender_ptr->p_sendlink;
return(OK);
}
prev_ptr = sender_ptr;
sender_ptr = sender_ptr->p_sendlink;
}
/* If the caller is a task, there may be an interrupt waiting.
* Check now, rather than before checking callers, so fast interrupts
* give the process a chance to absorb them. Lock between determining the
* state of busy_map and setting RECEIVING so an interrupt occurring
* between cannot be forgotten.
*/
if (caller < HARDWARE && (src == ANY || src == HARDWARE)) {
lock();
locked = TRUE;
this_bit = 1 << (-caller);
if (busy_map & this_bit) {
cp_mess(HARDWARE, proc[NR_TASKS+HARDWARE].p_map[D].mem_phys,
task_mess[-caller], caller_ptr->p_map[D].mem_phys,
m_ptr);
busy_map &= ~this_bit; /* must be locked here too */
restore();
return(OK);
}
}
}
/* No suitable message is available. Block the process trying to receive. */
caller_ptr->p_getfrom = src;
caller_ptr->p_messbuf = m_ptr;
caller_ptr->p_flags |= RECEIVING;
if (locked) restore();
unready(caller_ptr);
/* If MM has just blocked and there are kernel signals pending, now is the
* time to tell MM about them, since it will be able to accept the message.
* Also applies to revive messages for FS. No problems with receive-before-
* send, since (src == ANY) cannot be true for sendrec messages.
*/
if (rev_procs > 0 && caller == FS_PROC_NR && src == ANY) inform(FS_PROC_NR);
if (sig_procs > 0 && caller == MM_PROC_NR && src == ANY) inform(MM_PROC_NR);
return(OK);
}
===============
kernel\system.c
===============
/*===========================================================================*
* set_revive *
*===========================================================================*/
PUBLIC set_revive(proc_nr, status)
int proc_nr;
int status;
/* Similar function to cause_sig. Tasks sending a revive message directly
* to FS may collide with a send in the opposite direction. Therefore, this
* function saves the event and associated status (error code, bytes read etc)
* in the target process' proc entry, and the kernel informs FS later with
* inform(FS_PROC_NR). It would be nice to share the proc table's p_pending
* member with MM, but the signals recorded in it may be ignored - only MM
* knows. Note that the device's id is lost, and only FS may receive a message.
* This is not currently a problem.
*/
{
register struct proc *rp;
rp = proc_addr(proc_nr);
if (rp->p_flags & NEED_REVIVE)
panic ("cannot revive twice",proc_nr);
rp->p_flags |= NEED_REVIVE;
rp->p_rstatus = status;
rev_procs++;
inform(FS_PROC_NR); /* perhaps it's safe to do it now ? */
}
/*===========================================================================*
* inform *
*===========================================================================*/
PUBLIC inform(proc_nr)
int proc_nr; /* MM_PROC_NR or FS_PROC_NR */
{
/* When a signal is detected by the kernel (e.g., DEL), or generated by a task
* (e.g. clock task for SIGALRM), cause_sig() is called to set a bit in the
* p_pending field of the process to signal. Then inform() is called to see
* if MM is idle and can be told about it. Whenever MM blocks, a check is
* made to see if 'sig_procs' is nonzero; if so, inform() is called.
* Likewise REVIVE messages for FS. These are sent only when FS is ready to
* receive. Code here changed to check recipient is not blocked on send as
* well as receive.
*/
register struct proc *rp, *mmp;
int r;
/* If MM/FS is not waiting for new input, forget it. */
mmp = proc_addr(proc_nr);
if ( ((mmp->p_flags & RECEIVING) == 0) || mmp->p_getfrom != ANY) return;
/* If MM is waiting for new input, find a process with pending signals. */
if (proc_nr == MM_PROC_NR) {
for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
if (rp->p_pending != 0) {
m.m_type = KSIG;
m.PROC1 = rp - proc - NR_TASKS;
m.SIG_MAP = rp->p_pending;
sig_procs--;
if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK)
panic("can't inform MM : ", r);
rp->p_pending = 0; /* the ball is now in MM's court */
return;
}
}
/* If FS is waiting for new input, find a process with pending revive. */
else if (proc_nr == FS_PROC_NR) {
for (rp = proc_addr(0); rp < proc_addr(NR_PROCS); rp++)
if (rp->p_flags & NEED_REVIVE) {
m.m_type = REVIVE;
m.REP_PROC_NR = rp - proc - NR_TASKS;
m.REP_STATUS = rp->p_rstatus;
rev_procs--;
rp->p_flags &= ~NEED_REVIVE;
if ((r=mini_send(HARDWARE, proc_nr, &m)) != OK)
panic("can't inform FS : ", r);
return; /* the ball is now in FS's court */
}
}
}
===========
mm/signal.c
===========
/*===========================================================================*
* check_sig *
*===========================================================================*/
PRIVATE int check_sig(proc_id, sig_nr, send_uid)
int proc_id; /* pid of process to signal, or 0 or -1 */
int sig_nr; /* which signal to send (1-16) */
uid send_uid; /* identity of process sending the signal */
{
/* Check to see if it is possible to send a signal. The signal may have to be
* sent to a group of processes. This routine is invoked by the KILL system
* call, and also when the kernel catches a DEL or other signal. SIGALRM too.
*/
register struct mproc *rmp;
int count, send_sig;
unshort mask;
extern unshort core_bits;
if (sig_nr < 1 || sig_nr > NR_SIGS) return(EINVAL);
count = 0; /* count # of signals sent */
mask = 1 << (sig_nr - 1);
/* Search the proc table for processes to signal. Several tests are made:
* - if proc's uid != sender's, and sender is not superuser, don't signal
* - if specific process requested (i.e., 'procpid' > 0, check for match
* - if a process has already exited, it can't receive signals
* - if 'proc_id' is 0 signal everyone in same process group except caller
*/
for (rmp = &mproc[INIT_PROC_NR + 1]; rmp < &mproc[NR_PROCS]; rmp++ ) {
if ( (rmp->mp_flags & IN_USE) == 0) continue;
send_sig = TRUE; /* if it's FALSE at end of loop, don't signal */
if (send_uid != rmp->mp_effuid && send_uid != SUPER_USER)send_sig=FALSE;
if (proc_id > 0 && proc_id != rmp->mp_pid) send_sig = FALSE;
if (rmp->mp_flags & HANGING) send_sig = FALSE; /*don't wake the dead*/
if (proc_id == 0 && mp->mp_procgrp != rmp->mp_procgrp) send_sig = FALSE;
if (send_uid == SUPER_USER && proc_id == -1) send_sig = TRUE;
/* SIGALARM is a little special. When a process exits, a clock signal
* can arrive just as the timer is being turned off. Also, turn off
* ALARM_ON bit when timer goes off to keep it accurate.
*/
/* Change : only reset the ALARM_ON bit for processes that are
* going to get the signal - don't wipe out all the other alarms!
*/
if (send_sig && sig_nr == SIGALRM) {
if ( (rmp->mp_flags & ALARM_ON) == 0) continue;
rmp->mp_flags &= ~ALARM_ON;
}
if (send_sig == FALSE || rmp->mp_ignore & mask) continue;
/* If process is hanging on PAUSE, WAIT, tty, pipe, etc. release it. */
unpause(rmp - mproc); /* check to see if process is paused */
count++;
/* Send the signal or kill the process, possibly with core dump. */
sig_proc(rmp, sig_nr);
if (proc_id > 0) break; /* only one process being signalled */
}
/* If the calling process has killed itself, don't reply. */
if ((mp->mp_flags & IN_USE) == 0 || (mp->mp_flags & HANGING))dont_reply =TRUE;
return(count > 0 ? OK : ESRCH);
}
/*===========================================================================*
* set_alarm *
*===========================================================================*/
PUBLIC int set_alarm(proc_nr, sec)
int proc_nr; /* process that wants the alarm */
unsigned sec; /* how many seconds delay before the signal */
{
/* This routine is used by do_alarm() to set the alarm timer. It is also
* to turn the timer off when a process exits with the timer still on.
*/
int remaining;
m_sig.m_type = SET_ALARM;
/*--- m_sig.PROC_NR = proc_nr; ---*/
m_sig.CLOCK_PROC_NR = proc_nr; /* clock uses different member */
m_sig.DELTA_TICKS = HZ * sec;
if (sec != 0)
mproc[proc_nr].mp_flags |= ALARM_ON; /* turn ALARM_ON bit on */
else
mproc[proc_nr].mp_flags &= ~ALARM_ON; /* turn ALARM_ON bit off */
/* Tell the clock task to provide a signal message when the time comes. */
if (sendrec(CLOCK, &m_sig) != OK) panic("alarm er", NO_NUM);
remaining = (int) m_sig.SECONDS_LEFT;
return(remaining);
}
=========
fs/pipe.c
=========
/*===========================================================================*
* release *
*===========================================================================*/
PUBLIC release(ip, call_nr, count)
register struct inode *ip; /* inode of pipe */
int call_nr; /* READ or WRITE */
int count; /* max number of processes to release */
{
/* Check to see if any process is hanging on the pipe whose inode is in 'ip'.
* If one is, and it was trying to perform the call indicated by 'call_nr'
* (READ or WRITE), release it.
*/
register struct fproc *rp;
/* Search the proc table. */
for (rp = &fproc[0]; rp < &fproc[NR_PROCS]; rp++) {
if (rp->fp_suspended == SUSPENDED
&& (rp->fp_fd & BYTE) == call_nr
&& rp->fp_revived != REVIVING
&& rp->fp_filp[rp->fp_fd>>8]->filp_ino == ip) {
revive(rp - fproc, 0);
susp_count--; /* keep track of who is suspended */
if (--count == 0) return;
}
}
}
/*===========================================================================*
* do_unpause *
*===========================================================================*/
PUBLIC int do_unpause()
{
/* A signal has been sent to a user who is paused on the file system.
* Abort the system call with the EINTR error message.
*/
register struct fproc *rfp;
int proc_nr, task, susfd;
struct filp *f;
dev_nr dev;
extern struct filp *get_filp();
if (who > MM_PROC_NR) return(EPERM);
proc_nr = pro;
if (proc_nr < 0 || proc_nr >= NR_PROCS) panic("unpause err 1", proc_nr);
rfp = &fproc[proc_nr];
if (rfp->fp_suspended == NOT_SUSPENDED) return(OK);
task = -rfp->fp_task;
if (task != XPIPE) {
susfd = rfp->fp_fd >> 8;
f = rfp->fp_filp[susfd]; /* don't use get_filp - fp is wrong */
if (susfd > NR_FDS || susfd < 0 || f == NIL_FILP) {
panic("unpause err 4", proc_nr);
}
dev = f->filp_ino->i_zone[0]; /* device on which proc is hanging */
mess.TTY_LINE = (dev >> MINOR) & BYTE;
mess.PROC_NR = proc_nr;
mess.m_type = CANCEL;
if (sendrec(task, &mess) != OK) panic("unpause err 2", NO_NUM);
while (mess.REP_PROC_NR != proc_nr) {
revive(mess.REP_PROC_NR, mess.REP_STATUS);
if (receive(task, &m) != OK) panic("unpause err 3", NO_NUM);
}
revive(proc_nr, EINTR); /* signal interrupted call */
}
return(OK);
}
I haven't included my asynchronous tty driver, as I don't use IBM
hardware and anybody interested in Kermit on Minix probably has their own
ideas. However, an outline of the driver follows for anybody who might be
interested (I'm happy to answer any further queries or comments as I
continue this development).
The tty driver is split into three parts:
tty.c Contains the 'device independent' part of the original driver.
screen.c Contains hardware-specific code for console screen and
keyboard driver.
async.c Contains hardware-specific code for asynchronous serial
tty driver.
The tty_struct structure is extended, and now contains fields for ioctl
function addresses (so do_ioctl sets characters, modes etc then calls
the device - dependent code for baud rate, etc) and a single character
echo function for that port. Tty_ramqueue is a union of the integer queue
required for screen driving and a char queue for buffering output, ready
for async interrupts. The flag used for RUNNING / STOPPED indication is
now a bit mask, another bit being STALLED, to indicate that an XOFF has
been sent to the device on the other end of the serial line.
TANDEM flowcontrol is implemented in tty.c, flipping the STALLED bit
and echoing an XOFF or XON when the buffer contents cross high and
low water marks. Interrupt processing is sufficiently fast that XOFF
does not need to be sent if the overrun queue is getting full.
Transmitter ready interrupts cause a character to be read from tty_rwords
and written to the UART. When tty_rwords is empty, a message is sent to
the tty task, and the queue is refilled. In the meantime, the transmitter
is disabled to avoid continuous interrupts.
Characters and refill requests are both sent through the same interrupt
message - use of the suggested TTY_O_DONE message would cause other
interrupts to be overwritten and lost. The message is now a structure
consisting of a count, a queue of ints for character and line number,
and a bit map for refill requests. Thus refill requests are not lost
if the input queue is full. The kernel interrupt() call is only
made if the count of queued characters incremented from zero, or a bit
was set for the first time in the bitmap. This reduces the interrupt
processing time if an interrupt had already been sent, but had not been
processed.
A bug exists in the processing of input characters - DEL and QUIT send
a signal to the proper process only if it is the only login. The calculation
of the proc to be signalled as 'LOW_USER + line + 1' is wrong, since the
process slot above the first user is occupied not by the second user's first
shell but by the update process. For proper processing of tty signals in
a multiple-user (or multiple screen) system, FS should tell the tty task when
a tty is controlling a process family. Note that this can also be a problem
in Kermit, since when the external line is not in use, a DEL arriving
can be sent to all active processes in the system. I have solved this by
making use of open() calls to the tty driver to inform the driver of the
process slot number when a controlling tty (one opened by the INIT process)
is opened or closed by FS.